package com.atguigu.tingshu.album.service.impl;

import cn.hutool.core.util.IdUtil;
import com.atguigu.tingshu.album.service.TestService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Service;

import java.util.Arrays;
import java.util.concurrent.TimeUnit;

/**
 * @author: atguigu
 * @create: 2023-10-07 08:54
 */
@Slf4j
@Service
public class TestServiceImpl implements TestService {

    @Autowired
    private StringRedisTemplate redisTemplate;


    //@Override
    //public synchronized void testLock() {
    //    try {
    //        //1.从Redis缓存中获取key="num"的值  保证redis中存在"num"(手动提前在redis中创建key)
    //        String value = stringRedisTemplate.opsForValue().get("num");
    //        if (StringUtils.isBlank(value)) {
    //            return;
    //        }
    //        //2.对获取到值进行+1操作
    //        int num = Integer.parseInt(value);
    //        stringRedisTemplate.opsForValue().set("num", String.valueOf(++num));
    //
    //    } catch (NumberFormatException e) {
    //        e.printStackTrace();
    //    }
    //}


    private static final String lock = "lock";
    //private static final String lockValue = "lock";

    /**
     * 自定义分布式锁
     */
    /*@Override
    public void testLock() {
        //一.获取分布式锁
        //1.1 尝试加锁 设置锁值为：UUID
        String lockValue = IdUtil.randomUUID();
        Boolean flag = redisTemplate.opsForValue().setIfAbsent(lock, lockValue, 5, TimeUnit.SECONDS);

        if (flag) {
            //二.获取锁成功执行业务代码
            try {
                //1.从Redis缓存中获取key="num"的值  保证redis中存在"num"(手动提前在redis中创建key)
                String value = redisTemplate.opsForValue().get("num");
                if (StringUtils.isBlank(value)) {
                    return;
                }
                //2.对获取到值进行+1操作
                int num = Integer.parseInt(value);
                redisTemplate.opsForValue().set("num", String.valueOf(++num));

            } catch (NumberFormatException e) {
                e.printStackTrace();
            } finally {
                //三、释放锁 判断锁是否为当前线程自己的
                //if (lockValue.equals(redisTemplate.opsForValue().get(lock))) {
                //    //说明Redis中存的锁是当前线程
                //    redisTemplate.delete(lock);
                //}
                    String scriptText = "if redis.call(\"get\",KEYS[1]) == ARGV[1]\n" +
                        "then\n" +
                        "    return redis.call(\"del\",KEYS[1])\n" +
                        "else\n" +
                        "    return 0\n" +
                        "end";
                //创建脚本对象-封装lua脚本
                DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
                redisScript.setScriptText(scriptText);
                redisScript.setResultType(Long.class);
                redisTemplate.execute(redisScript, Arrays.asList(lock), lockValue);

            }
        } else {
            log.error("获取锁失败..自旋.业务要求所有线程都得执行成功该方法");
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            this.testLock();
        }

    }*/

    @Autowired
    private RedissonClient redissonClient;


    /**
     * 基于Redisson提供分布式锁
     */
    @Override
    public void testLock() {
        //一、获取分布式锁
        //1.1 创建锁对象 入参为锁的名称-存入Redis
        RLock myLock = redissonClient.getLock("myLock");

        //1.2 获取锁
        //lock()当前线程会一直获取到锁为止  leaseTime入参值：-1
        myLock.lock();
        //lock(时间,时间单位) 当前线程会一直获取到锁为止，设置锁过期时间
        //myLock.lock(5, TimeUnit.SECONDS);

        //tryLock(等待时间，时间单位) 在等待时间内获取锁失败，返回false
        //boolean flag = myLock.tryLock(5, TimeUnit.SECONDS);

        //tryLock(等待时间，锁过期时间，时间单位) 在等待时间内获取锁失败，返回false，获取锁成功，设置锁过期时间
        //boolean flag = myLock.tryLock(5, 3, TimeUnit.SECONDS);

        //if (flag) {
        //二、获取锁成功执行业务
        try {
            //1.从Redis缓存中获取key="num"的值  保证redis中存在"num"(手动提前在redis中创建key)
            String value = redisTemplate.opsForValue().get("num");
            if (StringUtils.isBlank(value)) {
                return;
            }
            //2.对获取到值进行+1操作
            int num = Integer.parseInt(value);
            redisTemplate.opsForValue().set("num", String.valueOf(++num));

        } catch (NumberFormatException e) {
            e.printStackTrace();
        } finally {
            //三、释放锁
            myLock.unlock();
        }
        //} else {
        //    log.error("获取锁失败，结束or自旋");
        //}
    }


    /**
     * 修改数据前获取到写锁
     *
     * @param id
     */
    @Override
    public void write(Long id) {
        //1.获取写锁
        //1.1 创建读写锁对象
        RReadWriteLock rwlock = redissonClient.getReadWriteLock("myLock:" + id);
        //1.2 获取写锁
        RLock writeLock = rwlock.writeLock();
        writeLock.lock(5, TimeUnit.SECONDS);
        try {
            //2.写数据业务
            log.info("写数据....");
        } finally {
            //3.释放写锁
            //writeLock.unlock();
        }
    }

    /**
     * 读取数据前获取到读锁
     *
     * @param id
     */
    @Override
    public void read(Long id) {
        //1.获取读锁
        //1.1 创建读写锁对象
        RReadWriteLock rwlock = redissonClient.getReadWriteLock("myLock:" + id);
        //1.2 获取读锁
        RLock readLock = rwlock.readLock();
        readLock.lock(5, TimeUnit.SECONDS);
        //2.查询数据业务
        try {
            log.info("读取数据");
        } finally {
            //3.释放读锁
            //readLock.unlock();
        }
    }
}
